﻿using System;
using System.Threading;

namespace Heimdallr
{
    /// <summary>
    /// Provide methods and properties to controll and monitor a <see cref="ServiceElement"/> instance.
    /// </summary>
    /// <remarks>
    /// For the generic version of this class, see <see cref="ServiceElementController{T}"/>.
    /// </remarks>
    public class ServiceElementController
    {
        #region Field

        private readonly Lazy<ServiceElement> elementFactory;
        private readonly ServiceElement element;

        private TimeSpan restartDue;

        #endregion

        #region Property

        /// <summary>
        /// Exposes access to the underlying element of the controller.
        /// </summary>
        public ServiceElement ElementBase
        {
            get { return this.elementFactory.Value; }
        }

        /// <summary>
        /// Gets or sets the time interval of restarting the element if it stops. 
        /// Specify negative value to disable this mechanism.
        /// </summary>
        public TimeSpan RestartDue
        {
            get { return this.restartDue; }
            set { this.restartDue = value; }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the ServiceElementController class, using an existing element instance.
        /// </summary>
        /// <param name="element">An existing element instance.</param>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="element"/> is null.</exception>
        public ServiceElementController(ServiceElement element)
        {
            if (element == null)
                throw new ArgumentNullException("element");

            this.element = element;
            this.elementFactory = new Lazy<ServiceElement>(() => this.element, true);
            this.restartDue = TimeSpan.FromSeconds(-1);
        }

        /// <summary>
        /// Initializes a new instance of the ServiceElementController class. The controlled element instance is lazily initialized.
        /// </summary>
        /// <param name="elementFactory">The delegate that is invoked to produce the lazily initialized element instance when it is needed.</param>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="elementFactory"/> is null.</exception>
        public ServiceElementController(Func<ServiceElement> elementFactory)
        {
            if (elementFactory == null)
                throw new ArgumentNullException("elementFactory");

            this.elementFactory = new Lazy<ServiceElement>(elementFactory, true);
            this.restartDue = TimeSpan.FromSeconds(-1);
        }

        #endregion

        #region Method

        /// <summary>
        /// Starts the element.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// <para>Property <see cref="ElementBase"/> is null.</para>
        /// <para>or</para>
        /// <para>Element is not in <see cref="ServiceElementStatus">Stopped</see> state.</para>
        /// </exception> 
        public void Start()
        {
            if (this.ElementBase == null)
                throw new InvalidOperationException("No service element to apply operation.");

            if (this.ElementBase.Status != ServiceElementStatus.Stopped)
                throw new InvalidOperationException("Element is not in Stopped state.");

            this.ElementBase.Error += this.ServiceElement_Error;
            this.ElementBase.Run();
        }

        /// <summary>
        /// Stops the element.
        /// </summary>
        public void Stop()
        {
            if (this.ElementBase == null)
                return;

            if (this.ElementBase.Status == ServiceElementStatus.Stopped)
                return;

            this.ElementBase.Error -= this.ServiceElement_Error;
            this.ElementBase.Stop();
        }

        /// <summary>
        /// Pauses the element.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// <para>Property <see cref="ElementBase"/> is null.</para>
        /// <para>or</para>
        /// <para>Element is not in <see cref="ServiceElementStatus">Running</see> state.</para>
        /// </exception> 
        /// <exception cref="NotSupportedException"><see cref="ServiceElement.CanPauseAndContinue"/> is false.</exception>
        public void Pause()
        {
            if (this.ElementBase == null)
                throw new InvalidOperationException("No service element to apply operation.");

            if (!this.ElementBase.CanPauseAndContinue)
                throw new NotSupportedException("Can not pause and continue.");

            if (this.ElementBase.Status != ServiceElementStatus.Running)
                throw new InvalidOperationException("Element is not in Running state.");

            this.ElementBase.Pause();
        }

        /// <summary>
        /// Continues the element.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// <para>Property <see cref="ElementBase"/> is null.</para>
        /// <para>or</para>
        /// <para>Element is not in <see cref="ServiceElementStatus">Paused</see> state.</para>
        /// </exception> 
        public void Continue()
        {
            if (this.ElementBase == null)
                throw new InvalidOperationException("No service element to apply operation.");

            if (this.ElementBase.Status != ServiceElementStatus.Paused)
                throw new InvalidOperationException("Element is not in Paused state.");

            this.ElementBase.Continue();
        }

        #endregion

        #region ServiceElement EventHandler

        private void ServiceElement_Error(object sender, ExceptionsEventArgs e)
        {
            if (this.restartDue >= TimeSpan.Zero)
                ThreadPool.QueueUserWorkItem(this.Restart);
        }

        #endregion

        #region Callback

        private void Restart(object state)
        {
            Thread.Sleep(this.restartDue);
            this.ElementBase.Run();
        }

        #endregion
    }

    /// <summary>
    /// Provide methods and properties to controll and monitor a <typeparamref name="TElement"/> instance.
    /// </summary>
    /// <typeparam name="TElement">A derived class of <see cref="ServiceElement"/>.</typeparam>
    /// <remarks>
    /// This class is the generic version of <see cref="ServiceElementController"/>.
    /// </remarks>
    public class ServiceElementController<TElement> : ServiceElementController
        where TElement : ServiceElement
    {
        #region Property

        /// <summary>
        /// Exposes access to the underlying <typeparamref name="TElement"/> instance of the controller.
        /// </summary>
        public TElement Element
        {
            get { return base.ElementBase as TElement; }
        }

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the ServiceElementController class, using an existing element instance.
        /// </summary>
        /// <param name="element">An existing element instance.</param>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="element"/> is null.</exception>
        public ServiceElementController(TElement element)
            : base(element)
        {
        }

        /// <summary>
        /// Initializes a new instance of the ServiceElementController class. The controlled element instance is lazily initialized.
        /// </summary>
        /// <param name="elementFactory">The delegate that is invoked to produce the lazily initialized element instance when it is needed.</param>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="elementFactory"/> is null.</exception>
        public ServiceElementController(Func<TElement> elementFactory)
            : base(elementFactory)
        {
        }

        #endregion
    }
}
